58. 在线教育 - 讲师模块开发

创建父工程

创建父工程 edu-parent,删除 src 目录

添加 packaging 配置为 pom,并将 spring-boot-starter 改为 spring-boot-starter-web

添加依赖管理(管理公共依赖的 jar 包的版本)

创建子模块

选择 sprint 的方式或者 maven 额创建都可以,但是推荐使用 maven 的方式,因为这样可以选择关联父工程。

创建启动类

1
2
3
4
5
6
7
8
9
10
11
package com.yanrs.me;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EduTeacherApplication {
public static void main(String[] args) {
SpringApplication.run(EduTeacherApplication.class, args);
}
}

添加配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 服务端口
server.port=8001

# 服务名
spring.application.name=ude-teacher

# 环境设置:dev、test、prod
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/edu?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--开发者工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
</dependencies>

代码生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.yanrs.edu.teacher;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
* @author
* @since 2018/12/13
*/
public class CodeGenerator {

@Test
public void run() {

// 1、创建代码生成器
AutoGenerator mbg = new AutoGenerator();

// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");

gc.setAuthor("rex"); // 代码作者 -----------------------【根据实际情况修改】 -----------------------
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I, 不添加名称类似于 IUserService
gc.setIdType(IdType.ID_WORKER); //主键策略
gc.setDateType(DateType.ONLY_DATE); //如果数据库中字段是日期类型,那么生成的实体类中的属性也是日期类型
gc.setSwagger2(true); //开启Swagger2模式

mbg.setGlobalConfig(gc);

// 3、数据源配置 -----------------------【根据实际情况修改】 -----------------------
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/edu");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mbg.setDataSource(dsc);

// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("teacher"); // -----------------------【根据实际情况修改】 -----------------------
pc.setParent("com.yanrs.edu"); // setModuleName 和 setParent 的值合起来为完成的包路径 com.yanrs.edu -----------------------【根据实际情况修改】 -----------------------

pc.setController("controller"); // 在 com.yanrs.edu 下生成 controller 包
pc.setEntity("entity"); // 在 com.yanrs.edu 下生成 entity 包
pc.setService("service"); // 在 com.yanrs.edu 下生成 service 包
pc.setMapper("mapper"); // 在 com.yanrs.edu 下生成 mapper 包
mbg.setPackageInfo(pc);

// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_teacher"); // 数据库表的名称, 如果是多个表 strategy.setInclude("表1", "表2"); -----------------------【根据实际情况修改】 -----------------------

strategy.setNaming(NamingStrategy.underline_to_camel); //数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel); //数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mbg.setStrategy(strategy);

// 6、执行
mbg.execute();
}
}

运行后就能在对应的包下生成对应的代码

接口开发-获取所有讲师

开发一个接口,测试项目是否能运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.yanrs.edu.teacher.controller;


import com.yanrs.edu.teacher.entity.Teacher;
import com.yanrs.edu.teacher.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* <p>
* 讲师 前端控制器
* </p>
*
* @author rex
* @since 2020-06-14
*/
@RestController
@RequestMapping("/edu/teacher")
public class TeacherController {
// 注入 TeacherService
@Autowired
private TeacherService teacherService;

// 查询所有讲师
@GetMapping
public List<Teacher> getAllTeacher() {
return teacherService.list(null);
}
}

默认的返回中,时间的显示都是带有时区的,可以在配置文件中进行配置,让其返回正常的时间格式。

1
2
3
# 时间显示
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

接口开发 - 讲师删除

实体类中添加为逻辑删除字段添加 TableLogic 注解

1
2
3
@TableLogic
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;

配置类中添加逻辑删除插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.yanrs.edu.teacher.config;

import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@MapperScan("com.yanrs.edu.teacher.mapper")
public class EduTeacherConfig {

/**
* 逻辑删除
* @return
*/
@Bean
public ISqlInjector iSqlInjector(){
return new LogicSqlInjector();
}
}

controller 中添加删除方法

1
2
3
4
5
// 删除讲师
@DeleteMapping("{id}")
public Boolean deleteTeacherById(@PathVariable("id") String id) {
return teacherService.removeById(id);
}

配置 Swagger2

确认引入了 swagger 的依赖,新建 Swagger 配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.yanrs.edu.teacher.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.build();
}

private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-讲师管理API文档")
.description("本文档描述了讲师管理微服务接口定义")
.version("1.0")
.contact(new Contact("jack", "http://atguigu.com", "1111@qq.com"))
.build();
}
}

创建公共模块

封装统一返回, 创建统一返回枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.yanrs.edu.common;

// 定义返回数据中使用的状态码
public enum ResponseCode {
SUCCESS(90000, "成功"),
FAIL(30010, "成功"),
ERROR(30011, "错误"),
NOT_FOUND(30012, "资源未找到"),
NOT_AUTHED(30013, "无权限,访问拒绝"),
PARAM_INVAILD(30014, "提交参数非法");

private Integer code;
private String message;

public Integer getCode() {
return code;
}

public String getMessage() {
return message;
}

public void setCode(Integer code) {
this.code = code;
}

public void setMessage(String message) {
this.message = message;
}

ResponseCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}

创建统一返回类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.yanrs.edu.common;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

// 定义具体的数据返回格式
@Data
public class R {
private Boolean success;
private Integer code;
private String message;
private Map<String, Object> data = new HashMap<>();

//操作成功,调用这个方法,返回成功的数据
public static R success() {
R r = new R();
r.setSuccess(true);
r.setCode(ResponseCode.SUCCESS.getCode());
r.setMessage("操作成功");
return r;
}

//操作失败,调用这个方法,返回失败的数据
public static R fail() {
R r = new R();
r.setSuccess(false);
r.setCode(ResponseCode.FAIL.getCode());
r.setMessage("操作失败");
return r;
}

//使用链式编程
public R success(Boolean success) {
this.setSuccess(success);
return this;
}

public R message(String message) {
this.setMessage(message);
return this;
}

public R code(Integer code) {
this.setCode(code);
return this;
}

public R data(String key, Object value) {
this.data.put(key, value);
return this;
}

public R data(Map<String, Object> map) {
this.setData(map);
return this;
}
}

在其他子工程中,引入 common 的依赖

1
2
3
4
5
6
<!--common 依赖 -->
<dependency>
<groupId>com.yanrs.me</groupId>
<artifactId>edu-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

接口开发-讲师分页查询

controller

1
2
3
4
5
6
7
// 分页查询讲师列表
@GetMapping("/list/{currentPage}/{pageSize}")
public R getPageTeacher(@PathVariable("currentPage") Integer currentPage, @PathVariable("pageSize") Integer pageSize) {
Page<Teacher> teacherPage = new Page<>(currentPage, pageSize);
teacherService.page(teacherPage, null);
return R.success().data("total", teacherPage.getTotal()).data("items", teacherPage.getRecords());
}

还要在配置类中配置分页插件,不然 total 值为空

1
2
3
4
5
6
7
8
/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

接口开发-讲师条件查询且分页

controller

1
2
3
4
5
6
7
// 分页多条件查询讲师列表
@GetMapping("/condition/{currentPage}/{pageSize}")
public R getConditionPageTeacher(@PathVariable("currentPage") Integer currentPage, @PathVariable("pageSize") Integer pageSize, TeacherListReqVo teacherListReqVo) {
Page<Teacher> teacherPage = new Page<>(currentPage, pageSize);
teacherService.getTeacherListByCondition(teacherPage, teacherListReqVo);
return R.success().data("total", teacherPage.getTotal()).data("items", teacherPage.getRecords());
}

接口开发 - 讲师新增

新建 AddTeacherReqVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yanrs.edu.teacher.entity.vo;

import lombok.Data;

@Data
public class AddTeacherReqVo {
private String name;
private String intro;
private Integer level;
private String career;
private String avatar;
private Integer sort;
private Boolean isDeleted;
}

Controller 中

1
2
3
4
5
6
7
8
9
10
11
12
// 添加讲师
@PostMapping
public R addTeacher(@RequestBody AddTeacherReqVo addTeacherReqVo) {
Teacher teacher = new Teacher();
BeanUtils.copyProperties(addTeacherReqVo, teacher);
boolean save = teacherService.save(teacher);
if (save) {
return R.success().data("teacher", teacher);
} else {
return R.fail();
}
}

还需在实体类上设置 gmtCreate 和 gmtModified 字段的自动填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.yanrs.edu.teacher.entity;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
* <p>
* 讲师
* </p>
*
* @author rex
* @since 2020-06-14
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("edu_teacher")
@ApiModel(value="Teacher对象", description="讲师")
public class Teacher implements Serializable {

private static final long serialVersionUID = 1L;

@ApiModelProperty(value = "讲师ID")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;

@ApiModelProperty(value = "讲师姓名")
private String name;

@ApiModelProperty(value = "讲师资历,一句话说明讲师")
private String intro;

@ApiModelProperty(value = "讲师简介")
private String career;

@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;

@ApiModelProperty(value = "讲师头像")
private String avatar;

@ApiModelProperty(value = "排序")
private Integer sort;

@TableLogic
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;

@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

新建 MybatisPlusMetaHandler 类继承 MetaObjectHandler 用于处理自动填充。这里特别要注意⚠️字段名称不是数据库中的,而是实体类中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yanrs.edu.teacher.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MybatisPlusMetaHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 注意,这里的字段名称不是数据库里面的,而是实体类里面的
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}

接口开发 - 讲师修改

新建 UpdateTeacherReqVo 继承自 AddTeacherReqVo

1
2
3
4
5
6
7
package com.yanrs.edu.teacher.entity.vo;

import lombok.Data;

@Data
public class UpdateTeacherReqVo extends AddTeacherReqVo{
}

controller 中

1
2
3
4
5
6
7
8
9
10
11
12
13
// 根据 ID 修改讲师信息
@PutMapping("{id}")
public R updateTeacherById(@PathVariable("id") String id, @RequestBody UpdateTeacherReqVo updateTeacherReqVo) {
Teacher teacher = new Teacher();
BeanUtils.copyProperties(updateTeacherReqVo, teacher);
teacher.setId(id);
boolean update = teacherService.updateById(teacher);
if (update) {
return R.success().data("teacher", teacher);
} else {
return R.fail();
}
}

统一异常处理

在 handler 中新建 GlobalExceptionHandler 类,用于进行统一异常处理,当发生异常的时候就能捕捉到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.yanrs.edu.teacher.handler;

import com.yanrs.edu.common.R;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {

/**
* 捕捉全局异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public R error(Exception e){
e.printStackTrace();
return R.error();
}

/**
* 处理特定异常
* @param e
* @return
*/
// 特定异常处理 ArithmeticException
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("除数不能为 0");
}
}

自定义异常

新建 EduException 类,继承 RuntimeException,类中有两个属性 code 和 message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yanrs.edu.teacher.handler;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 自定义异常
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EduException extends RuntimeException{
private Integer code; // 状态码
private String message; // 错误描述信息
}

在 GlobalExceptionHandler 对 EduException 进行处理,当发生 EduException 的时候,接口返回相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.yanrs.edu.teacher.handler;

import com.yanrs.edu.common.R;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {

/**
* 捕捉全局异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public R error(Exception e){
e.printStackTrace();
return R.error();
}

/**
* 处理特定异常
* @param e
* @return
*/
// 特定异常处理 ArithmeticException
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("除数不能为 0");
}

/**
* 处理自定义异常
* @param e
* @return
*/
@ExceptionHandler(EduException.class)
@ResponseBody
public R error(EduException e){
e.printStackTrace();
return R.error().message(e.getMessage()).code(e.getCode());
}
}

在需要使用的时候加上自定义异常即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 根据 ID 修改讲师信息
@PutMapping("{id}")
public R updateTeacherById(@PathVariable("id") String id, @RequestBody UpdateTeacherReqVo updateTeacherReqVo) {
// 没有 updateTeacherReqVo 抛出异常
if (updateTeacherReqVo==null){
throw new EduException(ResponseCode.PARAM_INVAILD.getCode(), ResponseCode.PARAM_INVAILD.getMessage());
}
Teacher teacher = new Teacher();
BeanUtils.copyProperties(updateTeacherReqVo, teacher);
teacher.setId(id);
boolean update = teacherService.updateById(teacher);
if (update) {
return R.success().data("teacher", teacher);
} else {
return R.fail();
}
}

日志

头像上传 OSS

controller

1
2
3
4
5
6
7
8
9
10
11
// 上传讲师头像
@PostMapping("/upload/avatar")
public R uploadAvatar(@RequestParam("file") MultipartFile file){
try {
String url = aliOss.UploadFile(file.getOriginalFilename(), file.getInputStream());
return R.success().data("url", url);
} catch (Exception e) {
e.printStackTrace();
return R.fail();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.yanrs.edu.teacher.components;


import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.UUID;

@Component
public class AliOss {
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;

@Value("${aliyun.oss.file.keyid}")
private String keyId;

@Value("${aliyun.oss.file.keysecret}")
private String keySecret;

@Value("${aliyun.oss.file.bucketname}")
private String bucketName;

@Value("${aliyun.oss.file.foldername}")
private String folderName;

public String UploadFile(String fileName, InputStream fileInputStream){
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, keyId, keySecret);
// 生成随机文件名, 并上传到日期文件夹下面
String uploadFileName = new DateTime().toString("yyyy/MM/dd") + "/" + UUID.randomUUID().toString().replace("-", "") + "_" + fileName;
// 上传文件流。
ossClient.putObject(bucketName, uploadFileName, fileInputStream);
// 关闭OSSClient。
ossClient.shutdown();
return "https://" + bucketName + "." + endpoint + "/" + uploadFileName;
}
}

POI

Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。

1
2
3
4
5
HSSF - 提供读写Microsoft Excel格式档案的功能。(.xls)
XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。(.xlsx)
HWPF - 提供读写Microsoft Word格式档案的功能。
HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。

创建一个普通的 maven 工程,并引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--xls-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>

<!--xlsx-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>

<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.yanrs.me;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;

import java.io.FileOutputStream;

public class PoiDemo {
@Test
public void testWrite() throws Exception {
// 1. 创建 workbook
// Workbook workbook = new HSSFWorkbook(); // 03 版本 excel 操作
Workbook workbook = new XSSFWorkbook(); // 07 版本 excel 操作

// 2. 根据 workbook 创建 sheet
Sheet sheet = workbook.createSheet("会员列表");

// 3. 根据 sheet 操作 row(行)
Row row = sheet.createRow(0); // 创建第一行(序号从0开始)

// 4. 根据行 row 操作 cell (列)
Cell cell = row.createCell(0); // 创建第一列(序号从0开始)

// 5. 向 cell 中设置值
cell.setCellValue("131398612389698163");

// 6. 写入到文件并保存
// FileOutputStream fileOutputStream = new FileOutputStream("/tmp/xxx.xls"); // 03 版本 excel 操作
FileOutputStream fileOutputStream = new FileOutputStream("/tmp/xxx.xlsx"); // 07 版本 excel 操作
workbook.write(fileOutputStream);

// 关闭流信息
fileOutputStream.close();
}
}

读操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void testRead() throws Exception {
// 1. 获取文件输入流
FileInputStream fileInputStream = new FileInputStream("/tmp/xxx.xlsx"); // 07 版本 excel 操作
// FileInputStream fileInputStream = new FileInputStream("/tmp/xxx.xlsx"); // 03 版本 excel 操作

// 2. 创建 workbook
XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream); // 07 版本 excel 操作
// HSSFWorkbook workbook = new HSSFWorkbook("/tmp/xxx.xlsx"); // 03 版本 excel 操作

// 3. 根据 workbook 获取 sheet
XSSFSheet sheet = workbook.getSheet("会员列表");

// 4. 根据 sheet 获取行
XSSFRow row = sheet.getRow(0);

// 5. 根据行获取列
XSSFCell cell = row.getCell(0);

// 6. 获取值
System.out.println(cell.getStringCellValue());

// 7. 关闭输入流
fileInputStream.close();
}

Excel 03 版本和 07 版本的区别在于,03 版本只能写入最多 65535 条数据,而 07 版本则没有限制。如果我们要写入的数据量很大,超过100万条甚至更多条,那么我们可以使用 SXSSF

代码地址

创建视频模块

因为阿里云视频 “Java上传SDK” 并没有在 Maven 仓库中,所以需要将下载的 aliyun-java-vod-upload-1.4.12.jar 安装到本地 maven 仓库中。切换到下载 jar 包目录,执行以下命令

1
mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.12 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.12.jar

引入刚刚安装的依赖

1
2
3
4
5
6
<!--aliyun-sdk-vod-upload 为手动安装到 Maven 仓库中的 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-vod-upload</artifactId>
<version>1.4.12</version>
</dependency>

创建 video 模块

创建完成之后如果 idea 识别这不是一个 springboot 工程,那么可以这么操作。将 java 目录修改为 Source Root,resource 目录修改为 Resource Root 。

修改完成之后可以拷贝一个配置文件到 resource 下面,看看配置文件是否发生变化。

如果还不生效,那么可以删除改模块,重新创建一个不同名字的模块。

在 pom 中引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<dependencies>
<!-- edu-common -->
<dependency>
<groupId>com.yanrs.me</groupId>
<artifactId>edu-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 阿里云视频 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.3.3</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-vod</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-vod-upload</artifactId>
<version>1.4.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
</dependencies>

项目加载读取配置文件

在项目一启动的时候就加载配置文件中的配置,并将配置的内容赋值给类中的常量,这样在后续使用的时候,直接类名.常量名直接使用即可。上面的头像上传 OSS 模块不是这么设计的,不是在项目启动的时候就加载了配置文件,而是每次调用的时候才去获取。这两种方式都是获取配置文件的方式,使用哪一种都可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.yanrs.edu.videotape.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

public class ConstantPropertiesUtil implements InitializingBean {
// 获取配置文件中值
@Value("${aliyun.vod.file.keyid}")
private String keyId;

// 获取配置文件中值
@Value("${aliyun.vod.file.keysecret}")
private String keySecret;

// 定义两个常量
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;

@Override
public void afterPropertiesSet() {
// 将获取的配置文件中的值赋予常量
ACCESS_KEY_ID = keyId;
ACCESS_KEY_SECRET = keySecret;
}
}

Nginx 反向代理

因为 teacher 模块相关的路由后端使用的是 8001 端口,视频模块后端使用的是 8002 端口。而只能在前端配置一个端口,所以需要借助 nginx,让其对路由进行区分,不同的路由访问不同的端口。目前所有路由信息如下:

nginx 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
listen 9001;
server_name localhost;

location ~/edu/course/ {
proxy_pass http://192.168.1.3:8001;
}

location ~/edu/subject/ {
proxy_pass http://192.168.1.3:8001;
}

location ~/edu/chapter/ {
proxy_pass http://192.168.1.3:8001;
}

location ~/edu/video/ {
proxy_pass http://192.168.1.3:8001;
}

location ~/edu/teacher/ {
proxy_pass http://192.168.1.3:8001;
}

location ~/edu/videotape/ {
proxy_pass http://192.168.1.3:8002;
}
}

ngxin docker 启动命令如下

1
docker run -d -p 9001:9001 -p 8080:8080 --name nginx -v /Users/rex/Documents/server/nginx/www/:/usr/share/nginx/html -v /Users/rex/Documents/server/nginx/conf/nginx.conf:/etc/nginx/nginx.conf docker.io/nginx

这样配置之后,前端只需要访问 9001 端口即可,nginx 会根据不同的 url 地址来访问不同的后端接口。

1
2
3
4
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
BASE_API: '"http://127.0.0.1:9001"',
})

创建注册中心

父工程 pom 文件中指定Spring Cloud 版本

1
2
3
4
5
6
7
8
9
10
11
12
<dependencyManagement>
<dependencies>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

创建 edu-register 模块,在 pom 文件中增加 eureka-server 的依赖

1
2
3
4
5
6
7
<dependencies>
<!--注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

创建启动类,并添加注解

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.yanrs.edu.register;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EduRegisterApplication {
public static void main(String[] args) {
SpringApplication.run(EduRegisterApplication.class, args);
}
}

增加配置文件

1
2
3
4
5
6
7
8
9
#服务端口
server.port=8000

#是否将自己注册到Eureka服务器中,本身是服务器,无需注册
eureka.client.register-with-eureka=false
#是否从Eureka中获取注册信息
eureka.client.fetch-registry=false
#Eureka客户端与Eureka服务端进行通信的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:${server.port}/eureka/

启动注册中心,测试地址 http://127.0.0.1:8000/ 能否访问成功

注册到注册中心

在 teacher,videotape 的 pom 文件中添加 eureka-client 依赖

1
2
3
4
5
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在 teacher,videotape 的配置文件中添加配置

1
2
3
4
#指定注册中心地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8000/eureka/
#eureka服务器上获取的是服务器的ip地址,否则是主机名
eureka.instance.prefer-ip-address=true

在 teacher,videotape 的启动类中添加注解

1
@EnableEurekaClient

最后,先启动注册中心,在启动 teacher,videotape 服务,访问注册中心页面地址 http://127.0.0.1:8000/ 观察服务是否注册到其中

服务调用

在 teacher 中,调用 videotape 中的服务。在 teacher 中添加 feign 的注解,并在启动类中加上 @EnableFeignClients 注解

1
2
3
4
5
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1
@EnableFeignClients

新建 feign 模块,并在里面新建 VideotapeFeign 接口,接口中有需要被远程调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yanrs.edu.teacher.feign;

import com.yanrs.edu.common.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient("edu-videotape") // 要调用的服务
public interface VideotapeFeign {

// 要调用的方法
@DeleteMapping("edu/videotape/{id}")
public R deleteVideoById(@PathVariable("id") String id);
}